#define _CRT_SECURE_NO_WARNINGS
#include <limits.h> // for XXX_MAX
#include <memory.h> // for memset, memcpy
#include <stdio.h> // for FILE
#include "avif/avif.h"
#pragma comment(linker, "/export:avifFree")

static void copyRows(char* dst, const char* src, const int width, const int srcWidthStride, const int height) {
	for (int y = 0; y < height; y++) {
		memcpy(dst, src, width);
		dst += width;
		src += srcWidthStride;
	}
}

extern "C" __declspec(dllexport) int __cdecl avifDecode(const void* const pSrcData, const int srcSize,
	void** const ppDstData, int* const pDstWidth, int* const pDstHeight, int* const pDstDepth) {
	if (ppDstData)
		*ppDstData = 0;
	if (pDstWidth)
		*pDstWidth = 0;
	if (pDstHeight)
		*pDstHeight = 0;
	if (pDstDepth)
		*pDstDepth = 0;
	if (!pSrcData)
		return -1;
	if (srcSize <= 0)
		return -2;
	avifDecoder* const decoder = avifDecoderCreate();
	if (!decoder)
		return -3;
	decoder->strictFlags = AVIF_STRICT_DISABLED;
	decoder->codecChoice = AVIF_CODEC_CHOICE_DAV1D;
	avifResult r = avifDecoderSetIOMemory(decoder, (const uint8_t*)pSrcData, (size_t)srcSize);
	if (r != AVIF_RESULT_OK) {
		avifDecoderDestroy(decoder);
		return -100 - r;
	}
	r = avifDecoderParse(decoder);
	if (r != AVIF_RESULT_OK) {
		avifDecoderDestroy(decoder);
		return -200 - r;
	}
	if (decoder->imageCount < 1) {
		avifDecoderDestroy(decoder);
		return -4;
	}
	r = avifDecoderNextImage(decoder);
	if (r != AVIF_RESULT_OK) {
		avifDecoderDestroy(decoder);
		return -300 - r;
	}
	const avifImage* const image = decoder->image;
	if (pDstWidth)
		*pDstWidth = (int)image->width;
	if (pDstHeight)
		*pDstHeight = (int)image->height;
	int depth = image->alphaPlane != 0;
	if (image->yuvFormat == AVIF_PIXEL_FORMAT_YUV400)
		depth++;
	else if (image->yuvFormat == AVIF_PIXEL_FORMAT_YUV420
		  || image->yuvFormat == AVIF_PIXEL_FORMAT_YUV444
		  || image->yuvFormat == AVIF_PIXEL_FORMAT_YUV422)
		depth += 3;
	if (pDstDepth)
		*pDstDepth = depth;
	if (ppDstData && depth > 0) {
		void* const pDstData = avifAlloc((size_t)image->width * image->height * depth);
		if (!pDstData) {
			avifDecoderDestroy(decoder);
			return -5;
		}
		if (depth == 1) {
			if (image->depth > 8) { //TODO
				avifFree(pDstData);
				avifDecoderDestroy(decoder);
				return -6;
			}
			if (image->yuvFormat == AVIF_PIXEL_FORMAT_YUV400) {
				copyRows((char*)pDstData, (const char*)image->yuvPlanes[AVIF_CHAN_Y],
					image->width, image->yuvRowBytes[AVIF_CHAN_Y], image->height);
			} else {
				copyRows((char*)pDstData, (const char*)image->alphaPlane,
					image->width, image->alphaRowBytes, image->height);
			}
		} else if (depth == 2) { //TODO
			avifFree(pDstData);
			avifDecoderDestroy(decoder);
			return -7;
		} else {
			avifRGBImage rgb;
			memset(&rgb, 0, sizeof(rgb));
			avifRGBImageSetDefaults(&rgb, image);
			rgb.depth = 8;
			rgb.format = depth == 3 ? AVIF_RGB_FORMAT_RGB : AVIF_RGB_FORMAT_RGBA;
			rgb.pixels = (uint8_t*)pDstData;
			rgb.rowBytes = image->width * depth;
			r = avifImageYUVToRGB(image, &rgb);
			if (r != AVIF_RESULT_OK) {
				avifFree(pDstData);
				avifDecoderDestroy(decoder);
				return -400 - r;
			}
		}
		*ppDstData = pDstData;
	}
	avifDecoderDestroy(decoder);
	return 0;
}

struct avifIOEx : avifIO {
	FILE* fp;
	uint64_t offset;
	avifRWData buffer;
};

static void avifIOExDestroy(avifIO* const io) {
	avifIOEx* const reader = (avifIOEx*)io;
	if (reader->fp) {
		fclose(reader->fp);
		reader->fp = 0;
	}
	avifRWDataFree(&reader->buffer);
	avifFree(io);
}

static avifResult avifIOExRead(avifIO* const io, const uint32_t readFlags, const uint64_t offset, size_t size, avifROData* const out) {
	if (readFlags != 0)
		return AVIF_RESULT_IO_ERROR; // unsupported

	avifIOEx* const reader = (avifIOEx*)io;
	if (!reader->fp || offset > reader->sizeHint)
		return AVIF_RESULT_IO_ERROR; // past the EOF
	const size_t availableSize = (size_t)(reader->sizeHint - offset);
	if (size > availableSize)
		size = availableSize;

	if (size > 0) {
		if (offset > LONG_MAX)
			return AVIF_RESULT_IO_ERROR;
		if (reader->buffer.size < size) {
			const avifResult r = avifRWDataRealloc(&reader->buffer, size);
			if (r != AVIF_RESULT_OK)
				return r;
		}
#ifdef _MSC_VER
		if (_fseeki64(reader->fp, (__int64)(reader->offset + offset), SEEK_SET) != 0)
#else
		if (fseek(reader->fp, (long)(reader->offset + offset), SEEK_SET) != 0)
#endif
			return AVIF_RESULT_IO_ERROR;
		const size_t bytesRead = fread(reader->buffer.data, 1, size, reader->fp);
		if (size != bytesRead) {
			if (ferror(reader->fp))
				return AVIF_RESULT_IO_ERROR;
			size = bytesRead;
		}
	}

	out->data = reader->buffer.data;
	out->size = size;
	return AVIF_RESULT_OK;
}

extern "C" __declspec(dllexport) avifDecoder* __cdecl avifOpenFile(const char* const filename, const uint64_t offset, uint64_t size) {
	avifIOEx* const reader = (avifIOEx*)avifAlloc(sizeof(avifIOEx));
	if (!reader)
		return (avifDecoder*)-1LL;
	memset(reader, 0, sizeof(*reader));
	reader->fp = fopen(filename, "rb");
	if (!reader->fp) {
		avifIOExDestroy(reader);
		return (avifDecoder*)-2LL;
	}
	if ((int64_t)size < 0) {
		fseek(reader->fp, 0, SEEK_END);
#ifdef _MSC_VER
		size = (uint64_t)_ftelli64(reader->fp);
#else
		size = (uint64_t)ftell(reader->fp);
#endif
		size -= offset;
		if ((int64_t)size < 0) {
			avifIOExDestroy(reader);
			return (avifDecoder*)-3LL;
		}
	}
	reader->offset = offset;
	reader->destroy = avifIOExDestroy;
	reader->read = avifIOExRead;
	reader->sizeHint = size;
	reader->persistent = AVIF_FALSE;
	if (avifRWDataRealloc(&reader->buffer, 1024) != AVIF_RESULT_OK) {
		avifIOExDestroy(reader);
		return (avifDecoder*)-4LL;
	}

	avifDecoder* const decoder = avifDecoderCreate();
	if (!decoder) {
		avifIOExDestroy(reader);
		return (avifDecoder*)-5LL;
	}
	decoder->strictFlags = AVIF_STRICT_DISABLED;
	decoder->codecChoice = AVIF_CODEC_CHOICE_DAV1D;
	if ((int64_t)decoder < 0) {
		avifDecoderDestroy(decoder);
		avifIOExDestroy(reader);
		return (avifDecoder*)-6LL;
	}
	avifDecoderSetIO(decoder, reader);
	const avifResult r = avifDecoderParse(decoder);
	if (r != AVIF_RESULT_OK) {
		avifDecoderDestroy(decoder);
		return (avifDecoder*)(-100LL - r);
	}
	return decoder;
}

extern "C" __declspec(dllexport) int __cdecl avifDecodeFile(avifDecoder* const decoder, const int frameIdx) {
	if ((int64_t)decoder < 0)
		return -1;
	const avifResult r = frameIdx >= 0 ? avifDecoderNthImage(decoder, frameIdx) : avifDecoderNextImage(decoder);
	if (r != AVIF_RESULT_OK) {
		avifDecoderDestroy(decoder);
		return -100 - r;
	}
	return 0;
}

extern "C" __declspec(dllexport) void __cdecl avifCloseFile(avifDecoder* const decoder) {
	if ((int64_t)decoder > 0)
		avifDecoderDestroy(decoder);
}
